// Copyright 2016 The Draco Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include <cinttypes>

#include "draco/compression/decode.h"
#include "draco/core/cycle_timer.h"
#include "draco/io/file_utils.h"
#include "draco/io/obj_encoder.h"
#include "draco/io/parser_utils.h"
#include "draco/io/ply_encoder.h"

namespace {

struct Options {
  Options();

  std::string input;
  std::string output;
};

Options::Options() {}

void Usage() {
  printf("Usage: draco_decoder [options] -i input\n");
  printf("\n");
  printf("Main options:\n");
  printf("  -h | -?               show help.\n");
  printf("  -o <output>           output file name.\n");
}

int ReturnError(const draco::Status &status) {
  printf("Failed to decode the input file %s\n", status.error_msg());
  return -1;
}

}  // namespace

#if 0
int main(int argc, char **argv) {
  Options options;
  const int argc_check = argc - 1;

  for (int i = 1; i < argc; ++i) {
    if (!strcmp("-h", argv[i]) || !strcmp("-?", argv[i])) {
      Usage();
      return 0;
    } else if (!strcmp("-i", argv[i]) && i < argc_check) {
      options.input = argv[++i];
    } else if (!strcmp("-o", argv[i]) && i < argc_check) {
      options.output = argv[++i];
    }
  }
  if (argc < 3 || options.input.empty()) {
    Usage();
    return -1;
  }

  std::vector<char> data;
  if (!draco::ReadFileToBuffer(options.input, &data)) {
    printf("Failed opening the input file.\n");
    return -1;
  }

  if (data.empty()) {
    printf("Empty input file.\n");
    return -1;
  }

  // Create a draco decoding buffer. Note that no data is copied in this step.
  draco::DecoderBuffer buffer;
  buffer.Init(data.data(), data.size());

  draco::CycleTimer timer;
  // Decode the input data into a geometry.
  std::unique_ptr<draco::PointCloud> pc;
  draco::Mesh *mesh = nullptr;
  auto type_statusor = draco::Decoder::GetEncodedGeometryType(&buffer);
  if (!type_statusor.ok()) {
    return ReturnError(type_statusor.status());
  }
  const draco::EncodedGeometryType geom_type = type_statusor.value();
  if (geom_type == draco::TRIANGULAR_MESH) {
    timer.Start();
    draco::Decoder decoder;
    auto statusor = decoder.DecodeMeshFromBuffer(&buffer);
    if (!statusor.ok()) {
      return ReturnError(statusor.status());
    }
    std::unique_ptr<draco::Mesh> in_mesh = std::move(statusor).value();
    timer.Stop();
    if (in_mesh) {
      mesh = in_mesh.get();
      pc = std::move(in_mesh);
    }
  } else if (geom_type == draco::POINT_CLOUD) {
    // Failed to decode it as mesh, so let's try to decode it as a point cloud.
    timer.Start();
    draco::Decoder decoder;
    auto statusor = decoder.DecodePointCloudFromBuffer(&buffer);
    if (!statusor.ok()) {
      return ReturnError(statusor.status());
    }
    pc = std::move(statusor).value();
    timer.Stop();
  }

  if (pc == nullptr) {
    printf("Failed to decode the input file.\n");
    return -1;
  }

  if (options.output.empty()) {
    // Save the output model into a ply file.
    options.output = options.input + ".ply";
  }

  // Save the decoded geometry into a file.
  const std::string extension = draco::parser::ToLower(
      options.output.size() >= 4
          ? options.output.substr(options.output.size() - 4)
          : options.output);

  if (extension == ".obj") {
    draco::ObjEncoder obj_encoder;
    if (mesh) {
      if (!obj_encoder.EncodeToFile(*mesh, options.output)) {
        printf("Failed to store the decoded mesh as OBJ.\n");
        return -1;
      }
    } else {
      if (!obj_encoder.EncodeToFile(*pc, options.output)) {
        printf("Failed to store the decoded point cloud as OBJ.\n");
        return -1;
      }
    }
  } else if (extension == ".ply") {
    draco::PlyEncoder ply_encoder;
    if (mesh) {
      if (!ply_encoder.EncodeToFile(*mesh, options.output)) {
        printf("Failed to store the decoded mesh as PLY.\n");
        return -1;
      }
    } else {
      if (!ply_encoder.EncodeToFile(*pc, options.output)) {
        printf("Failed to store the decoded point cloud as PLY.\n");
        return -1;
      }
    }
  } else {
    printf("Invalid extension of the output file. Use either .ply or .obj.\n");
    return -1;
  }
  printf("Decoded geometry saved to %s (%" PRId64 " ms to decode)\n",
         options.output.c_str(), timer.GetInMs());
  return 0;
}

#else

#include <sys/stat.h>
#include <boost/filesystem.hpp>

void UsageSaite() {
  printf("Usage: draco_decoder [options] -i input\n");
  printf("\n");
  printf("Main options:\n");
  printf("  -h | -?               show help.\n");
  printf("  -i <input>            input file or directory name.\n");
  printf("  -o <output>           output file or directory name.\n");
}

bool IsFile(const char *filePath)
{
     struct stat buffer;
     return (stat(filePath, &buffer) == 0 && S_ISREG(buffer.st_mode));
}

bool IsDirectory(const char *dirPath)
{
    struct stat buffer;
    return (stat(dirPath, &buffer) == 0 && S_ISDIR(buffer.st_mode));
}

int GetFilesNum(const char* dirPath)
{
    using namespace boost::filesystem;

    path the_path(dirPath);
    int cnt = std::count_if(
        boost::filesystem::directory_iterator(the_path),
        boost::filesystem::directory_iterator(),
        static_cast<bool(*)(const path&)>(boost::filesystem::is_regular_file));

    printf("\n");
    printf("********************************************************\n");
    printf("* The number of all files in current path is:%d\n", cnt);
    printf("********************************************************\n\n");
    return cnt;
}

int GetOutPutFilePath(const std::string &inputPath, std::string &outputPath)
{
    size_t pos = inputPath.rfind("/");
    if (pos == std::string::npos) {
        printf("no find '/'\n");
        return -1;
    }
    
    std::string fileName = inputPath.substr(pos+1, inputPath.size()-pos-5);
    outputPath = outputPath + fileName + ".ply";
    
    return 0;
}

int DecodeFile(const std::string &inputPath, std::string &outputPath)
{
    std::vector<char> data;
    if (!draco::ReadFileToBuffer(inputPath, &data)) {
        printf("Failed opening the input file.\n");
        return -1;
    }

    if (data.empty()) {
        printf("Empty input file.\n");
        return -1;
    }

    // Create a draco decoding buffer. Note that no data is copied in this step.
    draco::DecoderBuffer buffer;
    buffer.Init(data.data(), data.size());

    draco::CycleTimer timer;

    // Decode the input data into a geometry.
    std::unique_ptr<draco::PointCloud> pc;
    draco::Mesh *mesh = nullptr;
    auto type_statusor = draco::Decoder::GetEncodedGeometryType(&buffer);
    if (!type_statusor.ok()) {
        return ReturnError(type_statusor.status());
    }

    const draco::EncodedGeometryType geom_type = type_statusor.value();
    if (geom_type == draco::TRIANGULAR_MESH) {
        timer.Start();
        draco::Decoder decoder;
        auto statusor = decoder.DecodeMeshFromBuffer(&buffer);

        if (!statusor.ok()) {
            return ReturnError(statusor.status());
        }
        std::unique_ptr<draco::Mesh> in_mesh = std::move(statusor).value();
        timer.Stop();

        if (in_mesh) {
            mesh = in_mesh.get();
            pc = std::move(in_mesh);
        }
    } else if (geom_type == draco::POINT_CLOUD) {
        // Failed to decode it as mesh, so let's try to decode it as a point cloud.
        timer.Start();
        draco::Decoder decoder;
        auto statusor = decoder.DecodePointCloudFromBuffer(&buffer);
        if (!statusor.ok()) {
            return ReturnError(statusor.status());
        }
        
        pc = std::move(statusor).value();
        timer.Stop();
    }

    if (pc == nullptr) {
        printf("Failed to decode the input file.\n");
        return -1;
    }

    if (outputPath.empty()) {
        // Save the output model into a ply file.
        outputPath = inputPath + ".ply";
    }

    // Save the decoded geometry into a file.
    const std::string extension = draco::parser::ToLower(
        outputPath.size() >= 4
        ? outputPath.substr(outputPath.size() - 4)
        : outputPath);

    if (extension == ".obj") {
        draco::ObjEncoder obj_encoder;
        if (mesh) {
            if (!obj_encoder.EncodeToFile(*mesh, outputPath)) {
                printf("Failed to store the decoded mesh as OBJ.\n");
                return -1;
            }
        } else {
            if (!obj_encoder.EncodeToFile(*pc, outputPath)) {
                printf("Failed to store the decoded point cloud as OBJ.\n");
                return -1;
            }
        }
    } else if (extension == ".ply") {
        draco::PlyEncoder ply_encoder;
        if (mesh) {
            if (!ply_encoder.EncodeToFile(*mesh, outputPath)) {
                printf("Failed to store the decoded mesh as PLY.\n");
                return -1;
            }
        } else {
            if (!ply_encoder.EncodeToFile(*pc, outputPath)) {
                printf("Failed to store the decoded point cloud as PLY.\n");
                return -1;
            }
        }
    } else {
        printf("Invalid extension of the output file. Use either .ply or .obj.\n");
        return -1;
    }
    
//    printf("Decoded geometry saved to %s (%" PRId64 " ms to decode)\n",
//            outputPath.c_str(), timer.GetInMs());
    
    return 0;
}

int main(int argc, char **argv)
{
    Options options;
    const int argc_check = argc - 1;

    for (int i = 1; i < argc; ++i) {
        if (!strcmp("-h", argv[i]) || !strcmp("-?", argv[i])) {
            UsageSaite();
            return 0;
        } else if (!strcmp("-i", argv[i]) && i < argc_check) {
            options.input = argv[++i];
        } else if (!strcmp("-o", argv[i]) && i < argc_check) {
            options.output = argv[++i];
        }
    }

    if (argc < 3 || options.input.empty()) {
        UsageSaite();
        return -1;
    }

    bool isInputDir  = IsDirectory(options.input.c_str());
    bool isOutputDir = false;
    
    if (0 == access(options.output.c_str(), F_OK)) {
        isOutputDir = IsDirectory(options.output.c_str());
    }
    
    if (isInputDir != isOutputDir) {
        printf("ERROR: Input and output types are inconsistent:\n");
        printf("       The input is: %s\n", (isInputDir ? "directory" : "file"));
        printf("       The output is: %s\n\n", (isOutputDir ? "directory" : "file"));
        return -1;
    }

    draco::CycleTimer timer;
    timer.Start();
    
    if (isInputDir) {
        if ('/' != options.output.back()) {
            options.output += "/";
        }
        
        int decodeCnt = 0;
        int totalCnt = GetFilesNum(options.input.c_str());
        boost::filesystem::directory_iterator end_iter;
        for (boost::filesystem::directory_iterator iter(options.input); iter != end_iter ; iter++) {
            if (boost::filesystem::is_regular_file(*iter) and (*iter).path().string().find(".drc") != std::string::npos) {
                std::string output = options.output;
                GetOutPutFilePath((*iter).path().string(), output);
                DecodeFile((*iter).path().string(), output);
                decodeCnt++;
                printf("\rThe number of decoded files is:%d, completed rate:%.1f\%%", decodeCnt, 100.0 * decodeCnt / totalCnt);
                fflush(stdout);
            }
        }
        printf("\n");
    } else {
        DecodeFile(options.input, options.output);
    }

    timer.Stop();

    int64_t usageTime = timer.GetInMs();
    int64_t minute = usageTime / 60000;
    int64_t second = (usageTime % 60000) / 1000;
    int64_t millisecond = usageTime % 1000;
    
    printf("Decode all files usage time: %" PRId64 " min %" PRId64 " s %" PRId64 " ms \n\n", minute, second, millisecond);
    printf("---------- Decoding completed. ----------\n\n");
}

#endif

